#ifndef AER_TEXTURE_HPP_
#define AER_TEXTURE_HPP_

#include "aerDeviceResource.hpp"

// ~ wip

namespace aer {
  
class Texture : public DeviceResource
{
  public:
    enum Target
    {
      TEXTURE_1D          = GL_TEXTURE_1D,
      TEXTURE_2D          = GL_TEXTURE_2D,
      TEXTURE_3D          = GL_TEXTURE_3D,
      TEXTURE_1D_ARRAY    = GL_TEXTURE_1D_ARRAY,
      TEXTURE_2D_ARRAY    = GL_TEXTURE_2D_ARRAY,
      TEXTURE_RECTANGLE   = GL_TEXTURE_RECTANGLE,
      TEXTURE_CUBE_MAP    = GL_TEXTURE_CUBE_MAP,
      
      TEXTURE_BUFFER      = GL_TEXTURE_BUFFER
    };
  
    typedef int InternalFormat;
  /*
    enum InternalFormat
    {
      // Base format not set
      RGB8 = GL_RGB8
      
      R8,
      R8_SNORM,
      R16,
      R16_SNORM,
      RG8,
      RG8_SNORM,
      RG16,
      RG16_SNORM,
      R3_G3_B2,
      RGB4,
      RGB5,
      RGB8,
      RGB8_SNORM,
      RGB10,
      RGB12,
      RGB16_SNORM,
      RGBA2,
      RGBA4,
      RGB5_A1,
      RGBA8,
      RGBA8_SNORM,
      RGB10_A2,
      RGB10_A2UI,
      RGBA12,
      RGBA16,
      SRGB8,
      SRGB8_ALPHA8,
      R16F,
      RG16F,
      RGB16F,
      RGBA16F,
      R32F,
      RG32F,
      RGB32F,
      RGBA32F,
      R11F_G11F_B10F,
      RGB9_E5,
      R8I,
      R8UI,
      R16I,
      R16UI,
      R32I,
      R32UI,
      RG8I,
      RG8UI,
      RG16I,
      RG16UI,
      RG32I,
      RG32UI,
      RGB8I,
      RGB8UI,
      RGB16I,
      RGB16UI,
      RGB32I,
      RGB32UI,
      RGBA8I,
      RGBA8UI,
      RGBA16I,
      RGBA16UI,
      RGBA32I,
      RGBA32UI,

      DEPTH_COMPONENT16, 
      DEPTH_COMPONENT24, 
      DEPTH_COMPONENT32F
      
    };
    */
    
    enum Format
    {
      RED   = GL_RED,
      RG    = GL_RG,
      RGB   = GL_RGB,
      BGR   = GL_BGR,
      RGBA  = GL_RGBA,
      BGRA  = GL_BGRA,
      
      DEPTH_COMPONENT = GL_DEPTH_COMPONENT,
      DEPTH_STENCIL   = GL_DEPTH_STENCIL
    };
    
    enum Type
    {
      UNSIGNED_BYTE   = GL_UNSIGNED_BYTE,
      BYTE            = GL_BYTE,
      UNSIGNED_SHORT  = GL_UNSIGNED_SHORT,
      SHORT           = GL_SHORT,
      UNSIGNED_INT    = GL_UNSIGNED_INT,
      INT             = GL_INT,
      FLOAT           = GL_FLOAT
    };
    
    
    // ==============
    // + Parameters +
    // ==============
    enum WrapTarget
    {
      TEXTURE_WRAP_S = GL_TEXTURE_WRAP_S,
      TEXTURE_WRAP_T = GL_TEXTURE_WRAP_T,
      TEXTURE_WRAP_R = GL_TEXTURE_WRAP_R
    };
    
    enum WrapMode
    {
      CLAMP_TO_EDGE   = GL_CLAMP_TO_EDGE,
      CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER,
      MIRRORED_REPEAT = GL_MIRRORED_REPEAT,
      REPEAT          = GL_REPEAT
    };
    
    enum FilterTarget
    {
      TEXTURE_MIN_FILTER = GL_TEXTURE_MIN_FILTER,
      TEXTURE_MAG_FILTER = GL_TEXTURE_MAG_FILTER,
    };
    
    enum FilterMode
    {
      NEAREST = GL_NEAREST,
      LINEAR  = GL_LINEAR
    };
    
    enum CompareMode
    {
      LEQUAL    = GL_LEQUAL,
      GEQUAL    = GL_GEQUAL,
      LESS      = GL_LESS,
      GREATER   = GL_GREATER,
      EQUAL     = GL_EQUAL,
      NOTEQUAL  = GL_NOTEQUAL,
      ALWAYS    = GL_ALWAYS,
      NEVER     = GL_NEVER
    };
    
    enum CompareFunc
    {
      COMPARE_REF_TO_TEXTURE = GL_COMPARE_REF_TO_TEXTURE,
      NONE                   = GL_NONE
    };
    
    /*
    enum MipmapLevelTarget
    {
      TEXTURE_BASE_LEVEL = GL_TEXTURE_BASE_LEVEL,
      TEXTURE_MAX_LEVEL  = GL_TEXTURE_MAX_LEVEL
    };
    
    enum SwizzleTarget
    {
      TEXTURE_SWIZZLE_R     = GL_TEXTURE_SWIZZLE_R,
      TEXTURE_SWIZZLE_G     = GL_TEXTURE_SWIZZLE_G,
      TEXTURE_SWIZZLE_B     = GL_TEXTURE_SWIZZLE_B,
      TEXTURE_SWIZZLE_A     = GL_TEXTURE_SWIZZLE_A,
      TEXTURE_SWIZZLE_RGBA  = GL_TEXTURE_SWIZZLE_RGBA
    };
    */
    
    
  private:
    unsigned int    m_unit;//
    Target           m_target;
    InternalFormat  m_internalFormat;
    unsigned int    m_width;
    unsigned int    m_height;
    unsigned int    m_depth;
    unsigned int    m_bytesPerPixel;
    void            *m_pixels;
    
    
  public:      
    static
    void Activate(unsigned int unit)
    {
      glActiveTexture( GL_TEXTURE0 + unit );
    }
  
    static
    void Unbind(Target target=TEXTURE_2D, unsigned int unit=0u)
    {
      Activate( unit );
      glBindTexture( target, 0u);
    }
      
    static GLenum GetMipmapMode(FilterMode choice, FilterMode interpolation)
    {
      bool bNearestChoice        = (choice == NEAREST);
      bool bNearestInterpolation = (interpolation == NEAREST);
      
      GLenum mode;
      
      if (bNearestChoice) {
        mode = (bNearestInterpolation) ? GL_NEAREST_MIPMAP_NEAREST :
                                         GL_NEAREST_MIPMAP_LINEAR;
      } else {
        mode = (bNearestInterpolation) ? GL_LINEAR_MIPMAP_NEAREST :
                                         GL_LINEAR_MIPMAP_LINEAR;
      }
      
      return mode;
    }
  
  
  public:
    explicit Texture(Target target = TEXTURE_2D) 
        : DeviceResource(),
          m_unit(0u), 
          m_target(target),
          m_internalFormat(GL_RGB8),
          m_width(0u),
          m_height(0u),
          m_depth(0u),
          m_bytesPerPixel(0u),
          m_pixels(NULL)
    {}
    
    
    void generate()
    {
      AER_ASSERT( !isGenerated() );
      glGenTextures( 1, &m_id);
    }
    
    void release()
    {
      if (isGenerated()) 
      {
        glDeleteTextures( 1, &m_id);
        m_id = 0u;
      }
    }
    
    void bind(unsigned int unit=0u)
    {
      AER_ASSERT( isGenerated() );
      
      m_unit = unit;
      Activate( m_unit );
      glBindTexture( m_target, m_id);
    }
    
    void unbind()
    {
      Activate( m_unit );
      glBindTexture( m_target, 0u);
    }
    
    
    // ===========
    // + Getters +
    // ===========
    Target getTarget() const { return m_target; }    

    int getInternalFormat() const { return m_internalFormat; }
    unsigned int getWidth() const { return m_width; }
    unsigned int getHeight() const { return m_height; }
    unsigned int getDepth() const { return m_depth; }
    unsigned int getBPP() const { return m_bytesPerPixel; }
    const void* getPixels() const { return m_pixels; }
    
        
    // ===========
    // + Setters +
    // ===========    
    void setTarget(Target target) { m_target = target; }    
    
    void setImage( Target target, int internalFormat, U32 w, U32 h, int format, int type, const void *data)
    {
      AER_ASSERT( target == TEXTURE_2D );//
      
      m_target = target;
      m_width = w; 
      m_height = h; 
      m_depth = 0;
      m_internalFormat = internalFormat;//
            
      if (target == TEXTURE_2D) {
        glTexImage2D( m_target, 0, internalFormat, w, h, 0, format, type, data);
      }
    }
    
    void resize(int width, int height, int internalFormat, const void *data=0)
    {      
      GLuint format = GL_RED;
      int type = GL_UNSIGNED_BYTE;
      
      if (internalFormat == GL_DEPTH_COMPONENT   || 
          internalFormat == GL_DEPTH_COMPONENT16 || 
          internalFormat == GL_DEPTH_COMPONENT24 || 
          internalFormat == GL_DEPTH_COMPONENT32F ) 
      {
        format = GL_DEPTH_COMPONENT;
        type = GL_UNSIGNED_INT;
      }
      
      if (internalFormat == GL_DEPTH24_STENCIL8)
      {
        format = GL_DEPTH_STENCIL;
        type = GL_UNSIGNED_INT_24_8;
      }
      
      setImage( m_target, internalFormat, width, height, format, type, data);
    }
        

    // ==============
    // + Parameters +
    // ==============
    
    // ~ Wrap ~
    void setWrap(WrapTarget target, WrapMode mode) {
      glTexParameteri( m_target, target, mode );
    }
    
    void setWraps(WrapMode wrapS, 
                  WrapMode wrapT = REPEAT, 
                  WrapMode wrapR = REPEAT)
    {
      setWrap( TEXTURE_WRAP_S, wrapS);
      setWrap( TEXTURE_WRAP_T, wrapT);
      setWrap( TEXTURE_WRAP_R, wrapR);
    }
    
    // ~ Filter ~
    void setFilter(FilterTarget target, FilterMode mode)
    {
      glTexParameteri( m_target, target, mode);
    }
    
    void setFilters(FilterMode minMode, FilterMode magMode)
    {
      glTexParameteri( m_target, GL_TEXTURE_MIN_FILTER, minMode);
      glTexParameteri( m_target, GL_TEXTURE_MAG_FILTER, magMode);
    }
    
    void setMipmapFilter(FilterMode choice, FilterMode interpolation)
    {
      GLenum mode = GetMipmapMode( choice, interpolation);
      glTexParameteri( m_target, GL_TEXTURE_MIN_FILTER, mode);
    }
     
    
    // ~ Compare ~
    void setCompareFunc(CompareFunc func)
    {
      glTexParameteri( m_target, GL_TEXTURE_COMPARE_FUNC, func);
    }
    
    void setCompareMode(CompareMode mode)
    {
      glTexParameteri( m_target, GL_TEXTURE_COMPARE_MODE, mode);      
    }
    
    // ~ LOD ~
    void setLOD( const float minLOD, const float maxLOD, const float bias=0.0f)
    {
      setMinLOD( minLOD );
      setMaxLOD( maxLOD );
      setLODBias( bias );
    }
    
    void setMinLOD(const float lod)
    {
      glTexParameterf( m_target, GL_TEXTURE_MIN_LOD, lod);
    }
    
    void setMaxLOD(const float lod)
    {
      glTexParameterf( m_target, GL_TEXTURE_MAX_LOD, lod);
    }
    
    void setLODBias(const float bias)
    {
      glTexParameterf( m_target, GL_TEXTURE_LOD_BIAS, bias);
    }
    

    // ~ Border color ~
    void setBorderColor( const float r, const float g, const float b)
    {
      const GLfloat color[] = { r, g, b};
      glTexParameterfv( m_target, GL_TEXTURE_BORDER_COLOR, color);
    }
    
    
    //void setAnisotropyLevel(const unsigned int level);
    
    // glGenerateMipmap could be slow !!
    //void generateMipmaps();
    
    
    // ========
    // + Copy +
    // ========
    // TODO
};
  
} // aer

#endif // AER_TEXTURE_HPP_
